/*******************************************************************************/
/*                                                                             */
/*  Copyright (c) 2009: Peter Schregle,                                        */
/*  All rights reserved.                                                       */
/*                                                                             */
/*  This file is part of the Property Library.                                 */
/*                                                                             */
/*  Redistribution of the Property Library and use in source and binary forms, */
/*  with or without modification, are permitted provided that the following    */
/*  conditions are met:                                                        */
/*  1. Redistributions of source code must retain the above copyright notice,  */
/*     this list of conditions and the following disclaimer.                   */
/*  2. Redistributions in binary form must reproduce the above copyright       */
/*     notice, this list of conditions and the following disclaimer in the     */
/*     documentation and/or other materials provided with the distribution.    */
/*  3. Neither the name of Peter Schregle nor the names of other contributors  */
/*     may be used to endorse or promote products derived from this software   */
/*     without specific prior written permission.                              */
/*                                                                             */
/*  THIS SOFTWARE IS PROVIDED BY PETER SCHREGLE AND CONTRIBUTORS 'AS IS' AND   */
/*  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE      */
/*  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE */
/*  ARE DISCLAIMED. IN NO EVENT SHALL PETER SCHREGLE OR CONTRIBUTORS BE LIABLE */
/*  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL */
/*  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS    */
/*  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)      */
/*  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,        */
/*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN   */
/*  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE            */
/*  POSSIBILITY OF SUCH DAMAGE.                                                */
/*                                                                             */
/*******************************************************************************/


#ifndef __property_rwfe_h__
#define __property_rwfe_h__

#include <boost/operators.hpp>
#include <boost/function.hpp>

namespace pl {

/******************************************************************************/
/*                                                                            */
/* property_rwfe                                                              */
/*                                                                            */
/******************************************************************************/

template <
	/// The type of the property.
	typename T,
	/// The type of the parent.
	typename P>
/// A read-write property, backed by an internal field and firing read and write
/// events.
//!
//! You can read values from and write values to this property.
//!
//! The unit_steppable template takes and generates (==>) the following 
//! operators:
//! ++   ==>  ++(int), pre-increment versus post-increment, (incrementable),
//! --   ==>  --(int), pre-decrement versus post-decrement, (decrementable).
class property_rwfe
	: boost::unit_steppable<pl::property_rwfe<T, P>
	>
{
	/// The field that backs the property.
	T value_;

	/// The parent of the property.
	P * parent_;

public:

	/// The value type of the property.
	typedef T value_type;

	/// The parent type of the property.
	typedef P parent_type;

	/// Type of the on_get event.
	//!
	//! The on_get event is fired whenever the value of the property is read.
	//! When the read is carried out the on_get function is called with the
	//! signature void on_get_event(P const* parent, T const& value).
	//!
	//! If you handle the event, you are passed a const pointer to the parent of
	//! the property, as well as a const reference to the property value.
	typedef boost::function<void (P const*, T const&)> on_get_type;

	/// The on_get event.
	//!
	//! If you assign a matching function to this event, the function will be
	//! called whenever the event is fired.
	//!
	//! The on_get event is fired whenever the value of the property is read.
	//! When the read is carried out the on_get function is called with the
	//! signature void on_get_event(P const* parent, T const& value).
	//!
	//! If you handle the event, you are passed a const pointer to the parent of
	//! the property, as well as a const reference to the property value.
	static on_get_type on_get;

	/// Type of the on_set event.
	//!
	//! The on_set event is fired whenever the value of the property is written.
	//! When the write is carried out the on_set function is called with the
	//! signature void on_set_event(P * parent, T & value).
	//!
	//! If you handle the event, you are passed a pointer to the parent of the
	//! property, as well as a reference to the property value. You can change
	//! the value while inside your function, and the changed value will then
	//! be used as the new value.
	typedef boost::function<void (P *, T &)> on_set_type;

	/// The on_set event.
	//!
	//! If you assign a matching function to this event, the function will be
	//! called whenever the event is fired.
	//!
	//! The on_set event is fired whenever the value of the property is written.
	//! When the write is carried out the on_set function is called with the
	//! signature void on_set_event(P * parent, T & value).
	//!
	//! If you handle the event, you are passed a pointer to the parent of the
	//! property, as well as a reference to the property value. You can change
	//! the value while inside your function, and the changed value will then
	//! be used as the new value.
	static on_set_type on_set;

	/// Type of the on_changing event.
	//!
	//! The on_changing event is fired whenever the value of the property is 
	//! about to be changed. This happens only, if the new value of the property
	//! is different to what it was before. If the new value is the same as the
	//! current value, the on_changing event is not fired.
	//!
	//! When the property is about to change the on_changing function is called 
	//! with the signature void on_changing_event(P const* parent, T const& 
	//! current_value, T const& new_value).
	//!
	//! If you handle the event, you are passed a const pointer to the parent of 
	//! the property, as well as const references to the current and new 
	//! property values.
	typedef boost::function<void (P const*, T const&, T const&)> 
		on_changing_type;

	/// The on_changing event.
	//!
	//! If you assign a matching function to this event, the function will be
	//! called whenever the event is fired.
	//!
	//! The on_changing event is fired whenever the value of the property is 
	//! about to be changed. This happens only, if the new value of the property
	//! is different to what it was before. If the new value is the same as the
	//! current value, the on_changing event is not fired.
	//!
	//! When the property is about to change the on_changing function is called 
	//! with the signature void on_changing_event(P const* parent, T const& 
	//! current_value, T const& new_value).
	//!
	//! If you handle the event, you are passed a const pointer to the parent of 
	//! the property, as well as const references to the current and new 
	//! property values.
	static on_changing_type on_changing;

	/// Type of the on_changed event.
	//!
	//! The on_changed event is fired whenever the value of the property has
	//! been changed. This happens only, if the new value of the property was
	//! different to what it was before. If the new value is the same as
	//! the current value, the on_changed event is not fired.
	//!
	//! When the property has been changed the on_changed function is called 
	//! with the signature void on_changed_event(P const* parent, T const& 
	//! value).
	//!
	//! If you handle the event, you are passed a const pointer to the parent of 
	//! the property, as well as a const reference to the newly set property 
	//! value.
	typedef boost::function<void (P const*, T const&)> on_changed_type;

	/// The on_changed event.
	//!
	//! If you assign a matching function to this event, the function will be
	//! called whenever the event is fired.
	//!
	//! The on_changed event is fired whenever the value of the property has
	//! been changed. This happens only, if the new value of the property was
	//! different to what it was before. If the new value is the same as
	//! the current value, the on_changed event is not fired.
	//!
	//! When the property has been changed the on_changed function is called 
	//! with the signature void on_changed_event(P const* parent, T const& 
	//! value).
	//!
	//! If you handle the event, you are passed a const pointer to the parent of 
	//! the property, as well as a const reference to the newly set property 
	//! value.
	static on_changed_type on_changed;

private:

	T & get()
	{
		if (on_get) 
			on_get(parent_, value_);
		return value_;
	}

	T const& get() const
	{
		if (on_get) 
			on_get(parent_, value_);
		return value_;
	}

	void set(T const& new_value) 
	{
		T value = new_value;
		if (on_set)
			on_set(parent_, value);
		if (value_ != value)
		{
			if (on_changing)
				on_changing(parent_, value_, value);
			value_ = value;
			if (on_changed)
				on_changed(parent_, value_);
		}
	}

public:

	/// Default construction of the property.
	//!
	//! By default, this initializes the property to zero (whatever that may
	//! mean for the type T). If you do not want this default-initialization
	//! because of performance reasons, you can get away with it, when you
	//! define the PL_NO_DEFAULT_INITIALIZATION macro.
	property_rwfe()
#ifndef PL_NO_DEFAULT_INITIALIZATION
		: value_(T())
		, parent_(0)
#endif
	{ }

	/// Construction of the property given an initial value.
	property_rwfe(
		/// Initial value of the property.
		T const& value) 
		: value_(value)
		, parent_(0)
	{ }

	/// Copy construction.
	property_rwfe(pl::property_rwfe<T,P> const& other)
		: value_(T()) 
		, parent_(other.parent_)
	{ 
		set(other.get());
	}

	/// Assign a new value to the property.
	pl::property_rwfe<T,P> & operator =(
		/// The new value.
		pl::property_rwfe<T,P> const& other)
	{
		parent_ = other.parent_;
		set(other.get());
		return *this;
	}

	/// Assign a new value to the property.
	pl::property_rwfe<T,P> & operator =(
		/// The new value.
		T const& value)
	{
		set(value);
		return *this;
	}

	/// Member selection operator.
	//!
	//! This allows selection of a property of a property, i.e. chaining the
	//! access of properties.
	//!
	//! Overloading the dot (.) operator for this purpose would have been 
	//! preferred, but sadly the C++ language does not allow this.
	//!
	//! \return The address of this variable.
	T * operator ->()
	{
		return &value_;
	}

	/// Access the property value.
	//!
	//! \return The property value.
	operator T & ()
	{
		return get();
	}

	/// Increment.
	//!
	//! Through the use of boost::unit_steppable operator ++(int) - 
	//! post-increment - is also defined and implemented by calling this 
	//! operator.
	//!
	//! /return A reference to this object.
	pl::property_rwfe<T,P> & operator ++()
	{
		set(++get());
		return *this;
	}

	/// Decrement.
	//!
	//! Through the use of boost::unit_steppable operator --(int) - 
	//! post-decrement - is also defined and implemented by calling this 
	//! operator.
	//!
	//! /return A reference to this object.
	pl::property_rwfe<T,P> & operator --()
	{
		set(--get());
		return *this;
	}
};

template<typename T, typename P> 
typename pl::property_rwfe<T,P>::on_get_type pl::property_rwfe<T,P>::on_get;

template<typename T, typename P> 
typename pl::property_rwfe<T,P>::on_set_type pl::property_rwfe<T,P>::on_set;

template<typename T, typename P> 
typename pl::property_rwfe<T,P>::on_changing_type 
	pl::property_rwfe<T,P>::on_changing;

template<typename T, typename P> 
typename pl::property_rwfe<T,P>::on_changed_type 
	pl::property_rwfe<T,P>::on_changed;



} // namespace pl


#endif // __property_rwfe_h__
